package PRO;

import java.util.*;
import java.math.BigInteger;

public class Rsa {

    //Declaración de variables utilizadas en todo el proceso
    int tamanoBits;
    private BigInteger n, q, p, eulerF, e, d;
    private int descifrado;

    //Constructor de las claves a partir de p y q de un tamano de bits determinado
    public Rsa(int size, int descifradoTipo) {
        tamanoBits = size;
        descifrado = descifradoTipo;
        creaPrimos();
        creaClaves();
    }

    // Constructor para cuando se tienen p,q y e   
    public Rsa(BigInteger primo1, BigInteger primo2, BigInteger clavePublica, int descifradoTipo) {
        p = primo1;
        q = primo2;
        e = clavePublica;
        n = p.multiply(q);
        descifrado = descifradoTipo;
        creaEuler();
        creaClavePrivada();
    }

    //Metodo para obtener la funcion Euler de p y q
    public void creaEuler() {

        //A eulerF le asignamos el valor de (p-1)*(q-1)      
        eulerF = p.subtract(BigInteger.valueOf(1));
        eulerF = eulerF.multiply(q.subtract(BigInteger.valueOf(1)));
    }

    //Metodo para la clave privada a partir de la clave publica y euler(n)
    public void creaClavePrivada() {

        //Cuando tengamos un valor adecuado para e, en base a este generamos el
        //valor de d. Esta operación es equivalente a: d = e modInverse eulerF
        d = e.modInverse(eulerF);
    }

    //Este método genera números primos a partir de una función BigInteger,
    //tomando el numero de bits.
    public void creaPrimos() {

        // Se genera el número aleatorio, primo, con un tamaño de bits definido,
        // un valor de probabilidad y un número aleatorio
        p = new BigInteger(tamanoBits, 10, new Random());

        // Ciclo que permite comparar si los números aleatorios generados son
        // iguales. Si son iguales asigna un nuevo valor a q y vuelve a realizar
        // la comparación.
        do {
            q = new BigInteger(tamanoBits, 10, new Random());
        } while (q.compareTo(p) == 0);
    }

    //Metodo para la clave publica a partir de la funcion euler(n)
    public void creaClavePublica() {

        // Generamos un número aleatorio e y realizamos un ciclo de
        // reasignación a e, en cualquiera de estos dos casos: 1. Si e es mayor
        // o igual que eulerF reasigna e. 2. Si el máximo común divisor de e y
        // eulerF no es 1 entonces reasigna e. Cuando se cumplen las dos
        // condiciones quiere decir que el valor encontrado para e es menor y
        // coprimo de eulerF. 
        do {
            e = new BigInteger(2 * tamanoBits, new Random());
        } while ((e.compareTo(eulerF) != -1) || (AlgEuclideano(e, eulerF).compareTo(BigInteger.valueOf(1)) != 0));

    }

    // Este método genera números e y d (claves) a partir de p y q, teniendo
    // en cuenta las restricciones definidas para estás variables en la teoría.
    public void creaClaves() {

        // Obtenemos n con el valor de p*q
        n = p.multiply(q);
        creaEuler();
        creaClavePublica();
        creaClavePrivada();
    }

    //Metodo que recibe un mensaje y lo convierte a codigo ascii
    public BigInteger[] string2Ascii(String mensaje) {

        //Creamos elementos tipo byte y obtenemos el valor en byte de cada uno
        //de los caracteres de mensaje
        byte[] aux = new byte[1];
        byte[] mensajeBytes = mensaje.getBytes();
        BigInteger[] mensajeAscii = new BigInteger[mensajeBytes.length];

        // En cada una de las posiciones del arreglo mensajeAscii insertamos el byte
        // (ascii) de la letra correspondiente, convirtiendolo a BigInteger
        for (int i = 0; i < mensajeAscii.length; i++) {
            aux[0] = mensajeBytes[i];
            mensajeAscii[i] = new BigInteger(aux);
        }
        return mensajeAscii;
    }

    //Metodo que recibe un mensaje y lo cifra usando la clave publica
    public BigInteger[] cifrar(String mensaje) {

        BigInteger[] mensajeAscii = string2Ascii(mensaje);

        //Generamos un arreglo que guarde el texto encriptado
        BigInteger[] cifrado = new BigInteger[mensajeAscii.length];

        // LLenamos el arreglo aplicandole la operación adecuada con la clave
        // pública
        for (int i = 0; i < mensajeAscii.length; i++) {
            cifrado[i] = mensajeAscii[i].modPow(e, n);
        }
        return (cifrado);
    }

    public String ascii2String(BigInteger[] mensajeAscii) {

        char[] charArray = new char[mensajeAscii.length];

        // Ahora en el arreglo de char, llenamos cada una de las posiciones con
        // el valor ASCII del Big Integer (que primero es convertido en entero).
        for (int i = 0; i < charArray.length; i++) {
            charArray[i] = (char) (mensajeAscii[i].intValue());
        }
        return new String(charArray);
    }

    public String descifrar(BigInteger[] msnCifrado) {

        String resultado;
        if (descifrado == 2) {
            resultado = descifrarTCR(msnCifrado);
            System.out.println("DESCIFRADO CON TEOREMA CHINO USADO");
        } else {
            resultado = descifrarNormal(msnCifrado);
            System.out.println("DESCIFRADO NORMAL USADO");
        }
        return resultado;
    }

    //Este metodo decifra un texto previamente cifrado con la clave publica,
    //se utiliza entonces la clave privada para obtener el mensaje original
    private String descifrarNormal(BigInteger[] msnCifrado) {

        BigInteger[] descifrado = new BigInteger[msnCifrado.length];

        //procedemos a llenar el arreglo (descifrado) tomando cada valor cifrado
        //y usando la clave privada para obtener el mensaje
        for (int i = 0; i < descifrado.length; i++) {
            descifrado[i] = msnCifrado[i].modPow(d, n);
        }
        return (ascii2String(descifrado));
    }

    private String descifrarTCR(BigInteger[] msnCifrado) {

        /**
         * Arreglo que va a contener los datos descifrados
         */
        BigInteger[] descifrado = new BigInteger[msnCifrado.length];

        /**
         * Variables que nos permiten minizar el tiempo de ejecución
         */
        BigInteger dp, dq, eulerP, eulerQ;

        /**
         * dp=d mod (p-1)
         */
        eulerP = (p.subtract(BigInteger.valueOf(1)));
        dp = d.mod(eulerP);

        /**
         * dq=d mod (q-1)
         */
        eulerQ = (q.subtract(BigInteger.valueOf(1)));
        dq = d.mod(eulerQ);

	////Se llena el arreglo (descifrado) con el texto cifrado operado con 
        //la clave privada
        //Para esto utilizamos el menor valor entre dp y dq para realizar la 
        //conversión de una manera más rápida
        if (dp.compareTo(dq) == -1) {
            for (int i = 0; i < msnCifrado.length; i++) {
                descifrado[i] = msnCifrado[i].modPow(dp, p);
            }
        } else {
            for (int i = 0; i < msnCifrado.length; i++) {
                descifrado[i] = msnCifrado[i].modPow(dq, q);
            }
        }
        return (ascii2String(descifrado));
    }

    //Metodo que utiliza el algoritmo euclideano para obtener maximo comun divisor
    public static BigInteger AlgEuclideano(BigInteger a, BigInteger b) {

        //Si b es mayor que a, cambia el orden
        if (b.compareTo(a) == 1) {
            //Funcion recursiva cambiando el orden
            return AlgEuclideano(b, a);
        }

        //q=a/b
        BigInteger q = a.divide(b);

        //a = q*b + r --> r = a - q*b
        BigInteger r = a.subtract((q.multiply(b)));

        //Cuando el residuo es 0, el algoritmo termina
        if (r == BigInteger.ZERO) {
            return b;
        }

        //Si no se a llegado a 0, se repite el proceso con b y el residuo
        return AlgEuclideano(b, r);
    }

    // Creamos varias funciones que nos permiten retornar los valores de las 
    //variables globales
    public BigInteger varp() {
        return (p);
    }

    public BigInteger varq() {
        return (q);
    }

    public BigInteger vareulerF() {
        return (eulerF);
    }

    public BigInteger varn() {
        return (n);
    }

    public BigInteger vare() {
        return (e);
    }

    public BigInteger vard() {
        return (d);
    }
}
